#include "lsp.h"
#include <stdio.h>
#include <vector>
#include <list>
#include <string.h>
#include "lex.h"

#define MTU 65507

using namespace std;

int max(int x, int y) { return (x > y)?x:y; }

class Job {
public:
	uint32_t client;
	vector<uint32_t> assignment;
	string upper;
	string lower;
	string hash;
	int len;
	string result;
	Job(const char * a, const char * b, const char * _hash) {
		upper = string(a);
		lower = string(b);
		len = max(upper.size(), lower.size());
		hash = string(_hash);
		result = "";
	}
	bool is_assigned(uint32_t worker) {
		for(unsigned int i = 0; i < assignment.size(); i++)
			if(assignment[i] == worker)
				return true;
		return false;
	}
	void retire(uint32_t worker) {
		vector<uint32_t>::iterator iter = assignment.begin();
		while(iter != assignment.end()) {
			if(*iter == worker)
				iter = assignment.erase(iter);
			else
				iter++;
		}
	}
};

class Server {
public:
	lsp_server* connection;
	list<Job> inactive_jobs;
	list<Job> active_jobs;
	list<uint32_t> inactive_workers;
	Server(int port) {
		connection = lsp_server_create(port);
	}
	void assign_next_job() {
		if(inactive_jobs.size() > 0 && inactive_workers.size() > 0) {
			Job j = inactive_jobs.front();
			inactive_jobs.pop_front();
			
			// Assign workers
			list<uint32_t>::iterator worker = inactive_workers.begin();
			while(worker != inactive_workers.end()) {
				j.assignment.push_back(*worker);
				worker = inactive_workers.erase(worker);
			}

			vector<string> parts = lex_divide(j.upper.c_str(), j.lower.c_str(), j.len, j.assignment.size());
			for(int i = 0; i < j.assignment.size(); i++) {
				char msg[MTU];
				memset(msg, 0, MTU);
				char tok[] = ":";
				strcat(msg, j.hash.c_str());
				strcat(msg, tok);
				strcat(msg, parts[i].c_str());
				strcat(msg, tok);
				strcat(msg, parts[i+1].c_str());
				unsigned int len = strlen(msg);
				lsp_server_write(connection, (uint8_t *)msg, strlen((const char *)msg), j.assignment[i]);
			}
			active_jobs.push_back(j);
		}
	}
	void read() {
		uint32_t conn_id;
		char buf[MTU];
		memset(buf, 0, MTU);
		int bytes = lsp_server_read(connection, buf, &conn_id);
		
		if(bytes > 0) {
			//printf("Read from buffer: %s\n", buf);
			char c = buf[0];
			if(c == 'j') {
				// Worker joins
				inactive_workers.push_back(conn_id);
				printf("Worker %d joined.\n", conn_id);
			}
			else if(c == 'c') {
				// New job
				char * hash;
				char * upper;
				char * lower;
				strtok(buf, ":");
				hash = strtok(NULL, ":");
				lower = strtok(NULL, ":");
				upper = strtok(NULL, "\n");
				//printf("%s \n%s \n%s \n", hash, lower, upper); 
				Job new_job(lower, upper, hash);
				new_job.client = conn_id;
				inactive_jobs.push_back(new_job);
				printf("Request from client %d.\n", conn_id);
			}
			else if(c == 'f') {
				// Password found
				list<Job>::iterator job = active_jobs.begin();
				while(job != active_jobs.end() &&
					!job->is_assigned(conn_id))
					job++;
				if(job != active_jobs.end()) {
					lsp_server_write(connection, buf, bytes, job->client);
					strtok(buf, ":");
					char * pass = strtok(NULL, "\n");
					printf("Found password: %s\n", pass);
					job->retire(conn_id);
					inactive_workers.push_back(conn_id);
					job->result = string(pass);
				}
				
			}
			else if(c == 'x') {
				// Password not found
				list<Job>::iterator job = active_jobs.begin();
				while(job != active_jobs.end() && !job->is_assigned(conn_id))
					job++;
				if(job != active_jobs.end())
					job->retire(conn_id);
			}
		}
	}
	void retire_finished_jobs() {
		list<Job>::iterator job = active_jobs.begin();
		while(job != active_jobs.end()) {
			if(job->assignment.size() == 0) {
				if(job->result == "") {
					char *msg = "x";
					lsp_server_write(connection, msg, strlen(msg), job->client);
				}
				job = active_jobs.erase(job);
			}
			else
				job++;
		}
	}
	bool is_connected(uint32_t worker) {
		list<ClientData*>::iterator client = connection->server->connected_clients.begin();
		while(client != connection->server->connected_clients.end() &&
			(*client)->conn_id != worker)
			client++;
		return client != connection->server->connected_clients.end();
	}
	void retire_disconnected_workers() {
		// First search through inactive workers
		list<uint32_t>::iterator worker = inactive_workers.begin();
		while(worker != inactive_workers.end()) {
			if(!is_connected(*worker)) {
				worker = inactive_workers.erase(worker);
				printf("Worker %d disconnected.\n",*worker);
			}
			else
				worker++;
		}
		// Now search through jobs
		list<Job>::iterator job = active_jobs.begin();
		while(job != active_jobs.end()) {
			//worker = job->assignment.begin();
			vector<uint32_t>::iterator w = job->assignment.begin();
			while(w != job->assignment.end()) {
				if(!is_connected(*w)) {
					w = job->assignment.erase(w);
					printf("Worker %d disconnected.\n",*w);
				}
				else
					w++;
			}
			job ++;
		}
	}
};

int main(int argc, char ** argv) {
	int port = 8016;
	if(argc > 1)
		port = atoi(argv[1]);
	printf("Starting server\n");
	Server serv(port);
	
	bool running = true;

	while(running) {
		serv.read();
		serv.assign_next_job();
		serv.retire_disconnected_workers();
		serv.retire_finished_jobs();
	}
	
	return 0;
}
